const protocal = {
	protocol: "json",
	version: 1
};

const MessageType = {
	/** Indicates the message is an Invocation message and implements the {@link InvocationMessage} interface. */
	Invocation: 1,
	/** Indicates the message is a StreamItem message and implements the {@link StreamItemMessage} interface. */
	StreamItem: 2,
	/** Indicates the message is a Completion message and implements the {@link CompletionMessage} interface. */
	Completion: 3,
	/** Indicates the message is a Stream Invocation message and implements the {@link StreamInvocationMessage} interface. */
	StreamInvocation: 4,
	/** Indicates the message is a Cancel Invocation message and implements the {@link CancelInvocationMessage} interface. */
	CancelInvocation: 5,
	/** Indicates the message is a Ping message and implements the {@link PingMessage} interface. */
	Ping: 6,
	/** Indicates the message is a Close message and implements the {@link CloseMessage} interface. */
	Close: 7,
}


export class HubConnection {
	timeInterval = 15 //心跳间隔时间（秒）
	
	constructor() {
		this.openStatus = false;
		this.methods = {};
		this.negotiateResponse = {};
		this.connection = {};
		this.url = "";
		this.invocationId = 0;
		this.callbacks = {};
		this.interVal = 0; //全局定义定时执行心跳监测定时器ID，用于清除定时器
		this.reconnectTimer = function(){} //全局定义定时执行一次重连定时器ID，用于清除定时器
	}


	start(url, queryString) {
		var negotiateUrl = url + "/negotiate";
		if (queryString) {
			for (var query in queryString) {
				negotiateUrl += (negotiateUrl.indexOf("?") < 0 ? "?" : "&") + (`${query}=` + encodeURIComponent(
					queryString[query]));
			}
		}

		uni.request({
			url: negotiateUrl,
			method: "post",
			async: false,
			success: res => {
				this.negotiateResponse = res.data;
				this.startSocket(negotiateUrl.replace("/negotiate", ""));
			},
			fail: res => {
				console.error(`requrst ${url} error : ${res}`);
				return;
			}
		});

	}

	startSocket(url) {
		//清除心跳、重连定时器，停止心跳检测 
		// 停止发送心跳
		if(this.openStatus == true){
			clearTimeout(this.reconnectTimer)
			clearInterval(this.interVal)
		}
		
		// uni.closeSocket() //socketTaskChat
		
		url += (url.indexOf("?") < 0 ? "?" : "&") + ("id=" + this.negotiateResponse.connectionId);
		url = url.replace(/^http/, "ws");
		this.url = url;
		if (this.connection != null && this.openStatus) {
			return;
		}
		
		// 默认方式
		this.connection = uni.connectSocket({
			url: url,
			method: 'GET',
			success: res => {
				this.openStatus = true;
				this.handleHeart()
				
				uni.onSocketOpen((res) => {
					console.log('WebSocket连接已打开！');
					this.sendData(protocal)
					this.onOpen(res)
				});
				uni.onSocketError((res) => {
					console.log('WebSocket连接打开失败，请检查！', res);
					this.onError(res)
					//心跳监测
					this.handleHeart()
				});
				uni.onSocketMessage((res) => {
					// console.log('onSocketMessage',res)
					
					let message = {}
					try{
						let msg = res.data.replace(String.fromCharCode(0x1e), ""); //替换消息结束符
						message = JSON.parse(msg)
					}catch(e){
						// console.log(e)
					}
					this.receive(message)
				});
				uni.onSocketClose((res)=> {
					console.log('WebSocket 已关闭！');
					this.onClose(res)
				});
			},
			fail: () => {
				console.log('WebSocket连接打开失败，请检查！');
			},
			complete: () => {
				console.log("WebSocket complete")
			}
		});
	}

	on(method, fun) {
		let methodName = method.toLowerCase();
		if (this.methods[methodName]) {
			this.methods[methodName].push(fun);
		} else {
			this.methods[methodName] = [fun];

		}
	}

	onOpen(data) {}

	onClose(msg) {}

	onError(msg) {}
	
	onReconnect(msg) {
		console.log('重新连接socket',msg)
	}

	close(data) {
		if (data) {
			this.connection.close(data);
		} else {
			this.connection.close();
		}

		this.openStatus = false;
	}
	
	//心跳检测
	handleHeart() {
		// console.log("心跳监测开始")
		// let _this = this
		// if (this.openStatus == true) { //满足：已经开启过链接且中途断开方可重连
		// 	//设置定时开始心跳检测
		// 	this.interVal = setInterval(()=> {
		// 		var data = JSON.stringify({
		// 			type:"heart", 
		// 			data:"ping"
		// 		})
		// 		//发送消息，验证是否存在心跳
		// 		this.connection.send({
		// 			data: data,
		// 			success(res) {},
		// 			fail(res) {
		// 				console.log('发送失败')
		// 				//重连socekt
		// 				_this.reconnect()
		// 			}
		// 		})
		// 	}, this.timeInterval * 1000)
		// }
	}
	
	// 重新连接socket
	reconnect() {
		// 停止发送心跳
		clearTimeout(this.reconnectTimer)
		clearInterval(this.interVal)
		if (this.openStatus == true) { //满足：已经开启过链接且中途断开方可重连
			//定时执行一次websocket链接方法，进行socket重连
			this.reconnectTimer = setTimeout(() => {
				// console.log('尝试重新连接socket')
				//开启socket链接
				// that.webSocketConfig()
				this.onReconnect()
			}, this.timeInterval * 1000)
		}
	}
	
	sendData(data, success, fail, complete) {
		uni.sendSocketMessage({
			data: JSON.stringify(data) + String.fromCharCode(0x1e), //
			success: success,
			fail: fail,
			complete: complete
		})
	}

	receive(message) {
		switch (message.type) {
			case MessageType.Invocation:
				this.invokeClientMethod(message);
				break;
			case MessageType.StreamItem:
				break;
			case MessageType.Completion:
				var callback = this.callbacks[message.invocationId];
				if (callback != null) {
					delete this.callbacks[message.invocationId];
					callback(message);
				}
				break;
			case MessageType.Ping:
				// Don't care about pings
				break;
			case MessageType.Close:
				console.log("Close message received from server.");
				this.close({
					reason: "Server returned an error on close"
				});
				break;
			default:
				console.warn("Invalid message type: " + message.type);
		}
	}


	send(functionName) {

		var args = [];
		for (var _i = 1; _i < arguments.length; _i++) {
			args[_i - 1] = arguments[_i];
		}

		this.sendData({
			target: functionName,
			arguments: args,
			type: MessageType.Invocation,
			invocationId: this.invocationId.toString()
		});
		this.invocationId++;
	}


	invoke(functionName) {
		var args = [];
		for (var _i = 1; _i < arguments.length; _i++) {
			args[_i - 1] = arguments[_i];
		}

		var _this = this;
		var id = this.invocationId;
		var p = new Promise(function(resolve, reject) {

			_this.callbacks[id] = function(message) {
				if (message.error) {
					reject(new Error(message.error));
				} else {
					resolve(message.result);
				}
			}

			_this.sendData({
				target: functionName,
				arguments: args,
				type: MessageType.Invocation,
				invocationId: _this.invocationId.toString()
			}, null, function(e) {
				reject(e);
			});

		});
		this.invocationId++;
		return p;

	}

	invokeClientMethod(message) {
		var methods = this.methods[message.target.toLowerCase()];
		if (methods) {
			methods.forEach(m => m.apply(this, message.arguments));
			if (message.invocationId) {
				// This is not supported in v1. So we return an error to avoid blocking the server waiting for the response.
				var errormsg = "Server requested a response, which is not supported in this version of the client.";
				console.error(errormsg);
				this.close({
					reason: errormsg
				});
			}
		} else {
			console.warn(`No client method with the name '${message.target}' found.`);
		}
	}
}